#include "Stdafx.h"
#include "HuginApiAddons.h"

namespace ApiAddons {

	//Calculates difference in Aj for a single agent j node
	double* DeltaAj::CalculateSingleNode(double deltaAj, Domain *domain, Node *agentNode)
	{
		domain->updatePolicies();
		double baseEv = domain->getExpectedUtility();

		//Generate new tables
		NumberList baseTableData = agentNode->getTable()->getData();
		//vector<NumberList*> deltaAjTables;
		double difference[] = {0,0};

		int i = 0;
		for(NumberList::const_iterator it = baseTableData.begin(); it!= baseTableData.end(); ++it)
		{
			NumberList nl;

			for(int j = 0; j < baseTableData.size(); j++)
			{
				if(j == i)
				{
					nl.push_back(baseTableData[j] + deltaAj);
				}
				else
				{
					nl.push_back(baseTableData[j] - (deltaAj/(baseTableData.size() - 1)));
				}
			}


			//now apply table to node and get new ev
			domain->uncompile();
			agentNode->getTable()->setData(nl);
			domain->triangulate(H_TM_FILL_IN_WEIGHT);
			domain->compile();			

			//updating policies helps to determine what decision(s) will be made
			domain->updatePolicies();
			double tempdifference = baseEv - domain->getExpectedUtility();
			//tempdifference = abs(tempdifference);

			if (tempdifference > difference[0])
			{
				difference[0] = tempdifference;
			}
			if (tempdifference < difference[1])
			{
				difference[1] = tempdifference;
			}

			i++;
		}		

		cout << "Delta Ev+ = " << difference[0] << "\n";
		cout << "Delta Ev- = " << difference[1] << "\n";
		return difference;
	}

	//calculate the change in expected value for multiple aj nodes
	double* DeltaAj::CalculateMultipleNode(double deltaAj, Domain *domain, list<Node*> agentNodes)
	{
		domain->updatePolicies();
		double baseEv = domain->getExpectedUtility();
		double difference[] = {0,0};

		//first get combinations for aj
		list<int> sizes;		
		for(list<Node*>::iterator it = agentNodes.begin(); it != agentNodes.end(); ++it)
		{
			Node * node = *it;
			sizes.push_back(node->getTable()->getData().size());
		}
		CombinationGenerator cg;
		list<list<int>> combinations;
		combinations = cg.GenerateCombinations(sizes);

		//get starting tables before changing them to be able to set them back
		list<NumberList> startingTables = GetStartingTables(agentNodes);

		//apply each combination and note the difference
		for(list<list<int>>::iterator it = combinations.begin(); it != combinations.end(); ++it)
		{
			list<int> combination = *it;
			list<Node*>::iterator itn = agentNodes.begin();
			for(list<int>::iterator itc = combination.begin(); itc !=combination.end(); ++itc)
			{
				Node *agentNode = *itn;
				NumberList tableData = agentNode->getTable()->getData();
				NumberList nl;

				for(int j = 0; j < tableData.size(); j++)
				{
					if(*itc == j)
					{
						nl.push_back(tableData[j] + deltaAj);
					}
					else
					{
						nl.push_back(tableData[j] - (deltaAj/(tableData.size() - 1)));
					}
				}

				agentNode->getTable()->setData(nl);
				++itn;
			}

			domain->uncompile();
			domain->triangulate(H_TM_FILL_IN_WEIGHT);
			domain->compile();
			domain->updatePolicies();

			double tempdifference = baseEv - domain->getExpectedUtility();
			//tempdifference = abs(tempdifference);

			if (tempdifference > difference[0])
			{
				difference[0] = tempdifference;
			}	

			if (tempdifference < difference[1])
			{
				difference[1] = tempdifference;
			}

			//DomainPrinter dp;
			//dp.PrintDomain(domain);

			ResetStartingTables(startingTables, agentNodes);
		}

		cout << "Delta Ev+ = " << difference[0] << "\n";
		cout << "Delta Ev- = " << difference[1] << "\n";
		return difference;
	}

	//Gets the tables before they have been changed so they can be set back later
	list<NumberList> DeltaAj::GetStartingTables(list<Node*> agentNodes)
	{
		list<NumberList> startingTables;

		for(list<Node*>::iterator it = agentNodes.begin(); it != agentNodes.end(); ++it)
		{
			Node * node = *it;

			startingTables.push_back(node->getTable()->getData());
		}

		return startingTables;
	}

	//reset data tables
	void DeltaAj::ResetStartingTables(list<NumberList> tables, list<Node*> agentNodes)
	{
		list<Node*>::iterator ita = agentNodes.begin();

		for (list<NumberList>::iterator itn = tables.begin(); itn != tables.end(); ++itn)
		{
			NumberList nl = *itn;
			Node * node = *ita;

			node->getTable()->setData(nl);

			++ita;
		}
	}

	double* DeltaAj::CalculateDeltaEv(double deltaAj, Domain *domain, string agentJ)
	{
		NodeList nodes = domain->getNodes();
		list<Node*> aJNodes;

		//First need to isolate and Agent J chance nodes
		for (NodeList::const_iterator it = nodes.begin(); it != nodes.end(); ++it)
		{
			Node *node = *it;
			Category category = node->getCategory();

			if(category == H_CATEGORY_CHANCE)
			{
				//using the name of agent j to find what chance nodes
				//represent agent J
				if(node->getName().find(agentJ) != std::string::npos)
				{
					//add the Aj node to another list of nodes
					aJNodes.push_back(node);
				}
			}
		}

		if (aJNodes.size() == 1)
		{
			return CalculateSingleNode(deltaAj, domain, aJNodes.front());
		}
		else if (aJNodes.size() > 1)
		{
			return CalculateMultipleNode(deltaAj, domain, aJNodes);
		}
		else if (aJNodes.size() == 0)
		{
			cerr << "ID Contains no nodes for agent\n";
		}

		double difference[] = {0,0};
		return difference;
	}

	//Loads the domain from file then passes it to be processed
	double* DeltaAj::CalculateDeltaEv(double deltaAj, string netFileName, string agentJ)
	{
		DefaultParseListener pl;
		Domain domain(netFileName, &pl);
		string logFileName = netFileName + ".log";
		FILE * logFile = fopen(logFileName.c_str(), "w");

		if (logFile == NULL)
		{
			cerr << "Could not open \"" << logFileName << "\"\n";
			exit(EXIT_FAILURE);
		}

		domain.setLogFile(logFile);
		domain.triangulate(H_TM_FILL_IN_WEIGHT);
		domain.compile();
		domain.setLogFile(NULL);
		fclose(logFile);

		//ApiAddons::DomainPrinter dp;
		//dp.PrintDomain(&domain);

		return (CalculateDeltaEv(deltaAj, &domain, agentJ));
		//return 0.0;
	}
}
